#include "nfa.hpp"
#include "nfa_handler.hpp"

namespace nfa
{
    class Nmc
    {
    public:
        Nmc (const std::string& input) 
        { _lexer.start(input); }

        void expr(NfaPair* pairOut)
        {
            _catExpr(pairOut);
            while (_lexer.matchToken(lex::Token::OR)) {
                _lexer.advance();
                NfaPair* pairLocal = new NfaPair();
                _catExpr(pairLocal);
                Nfa* start = _nfa_handler.newNfa();
                Nfa* end = _nfa_handler.newNfa();
                start->_next1 = pairLocal->_start;
                start->_next2 = pairOut->_start;
                start->setEdge(Recv::EPSILON);
                pairLocal->_end->_next1 = end;
                pairOut->_end->_next1 = end;
                pairLocal->_end->setEdge(Recv::EPSILON);
                pairOut->_end->setEdge(Recv::EPSILON);
                pairOut->_start = start;
                pairOut->_end = end;
            }
        }

    private:
        bool _catExpr(NfaPair* pairOut)
        {
            if (_firstInCat(_lexer.getCurToken())) _factor(pairOut);
            while (_firstInCat(_lexer.getCurToken())) {
                NfaPair* pairLocal = new NfaPair(); 
                _factor(pairLocal);
                pairOut->_end->_next1 = pairLocal->_start;
                pairOut->_end->setEdge(Recv::EPSILON);
                pairOut->_end = pairLocal->_end;
            }
            return true;
        }

        void _factor(NfaPair* pairOut)
        {
            _term(pairOut);
            _starClosure(pairOut) || _plusClosure(pairOut) || _optionClosure(pairOut);
        }

        bool _firstInCat(lex::Token tok)
        {
            switch (tok) {
                case lex::Token::CLOSE_PAREN:  
                case lex::Token::AT_EOL:  
                case lex::Token::EOS:  
                    return false;
                case lex::Token::CLOSURE:
                case lex::Token::PLUS_CLOSE:
                case lex::Token::OPTIONAL:
                case lex::Token::CCL_END:
                    log::logMessage(log::LOG_LEVEL::_ERROR, "expression should not end with: %s", _lexer.getLexSym().c_str());
                    return false;
                case lex::Token::AT_BOL:
                    log::logMessage(log::LOG_LEVEL::_ERROR, "expression should not start with: %s", _lexer.getLexSym().c_str());
                    return false;
                case lex::Token::OR:
                    return false;
                default:
                    break;
            }
            return true;
        }

        bool _optionClosure(NfaPair* pairOut)
        {
            if (!_lexer.matchToken(lex::Token::OPTIONAL)) return false;
            Nfa* start = _nfa_handler.newNfa();
            Nfa* end = _nfa_handler.newNfa();
            start->_next1 = pairOut->_start;
            start->_next2 = end;
            start->setEdge(Recv::EPSILON);
            pairOut->_end->_next2 = end;
            pairOut->_end->setEdge(Recv::EPSILON);
            pairOut->_start = start;
            pairOut->_end = end;
            _lexer.advance();
            return true;
        }

        bool _starClosure(NfaPair* pairOut)
        {
            if (!_lexer.matchToken(lex::Token::CLOSURE)) return false;
            Nfa* start = _nfa_handler.newNfa();
            Nfa* end = _nfa_handler.newNfa();
            start->_next1 = pairOut->_start;
            start->_next2 = end;
            start->setEdge(Recv::EPSILON);
            pairOut->_end->_next1 = pairOut->_start;
            pairOut->_end->_next2 = end;
            pairOut->_end->setEdge(Recv::EPSILON);
            pairOut->_start = start;
            pairOut->_end = end;
            _lexer.advance();
            return true;
        }

        bool _plusClosure(NfaPair* pairOut)
        {
            if (!_lexer.matchToken(lex::Token::PLUS_CLOSE)) return false;
            Nfa* start = _nfa_handler.newNfa();
            Nfa* end = _nfa_handler.newNfa();
            start->_next1 = pairOut->_start;
            start->setEdge(Recv::EPSILON);
            pairOut->_end->_next2 = end;
            pairOut->_end->_next1 = pairOut->_start;
            pairOut->_end->setEdge(Recv::EPSILON);
            pairOut->_start = start;
            pairOut->_end = end;
            _lexer.advance();
            return true;
        }

        void _term(NfaPair* pairOut)
        {
            _exprInParen(pairOut) || _forSingleChar(pairOut) || _forDot(pairOut) || _forCharSet(pairOut);
        }

        bool _exprInParen(NfaPair* pairOut)
        {
            if (_lexer.matchToken(lex::Token::OPEN_PAREN)) {
                _lexer.advance();
                expr(pairOut);
                if (_lexer.matchToken(lex::Token::CLOSE_PAREN)) _lexer.advance();
                else log::logMessage(log::LOG_LEVEL::_ERROR, "paren not close");
                return true;
            }
            return false;
        }

        bool _forSingleChar(NfaPair* pairOut)
        {
            if (!_lexer.matchToken(lex::Token::L)) return false;
            Nfa* start = pairOut->_start = _nfa_handler.newNfa();
            pairOut->_end = pairOut->_start->_next1 = _nfa_handler.newNfa();
            start->setEdge(Recv::CCL);
            start->addToSet(_lexer.getLexeme());
            _lexer.advance();
            return true;
        }

        bool _forDot(NfaPair* pairOut)
        {
            if (!_lexer.matchToken(lex::Token::ANY)) return false;
            Nfa* start = pairOut->_start = _nfa_handler.newNfa();
            pairOut->_end = pairOut->_start->_next1 = _nfa_handler.newNfa();
            start->setEdge(Recv::CCL);
            start->addToSet((uint8_t)'\n');
            start->addToSet((uint8_t)'\r');
            start->setComplement();
            _lexer.advance();
            return true;
        }

        bool _forCharSet(NfaPair* pairOut)
        {
            if (!_lexer.matchToken(lex::Token::CCL_START)) return false;
            _lexer.advance();
            bool neg = false;
            if (_lexer.matchToken(lex::Token::AT_BOL)) {
                neg = true;
            }
            Nfa* start = pairOut->_start = _nfa_handler.newNfa();
            pairOut->_end = pairOut->_start->_next1 = _nfa_handler.newNfa();
            start->setEdge(Recv::CCL);
            if (!_lexer.matchToken(lex::Token::CCL_END)) _doDash(start->_input_set);
            if (!_lexer.matchToken(lex::Token::CCL_END)) std::cout << "forNoNeg Error" << std::endl;
            if (neg) start->setComplement();
            _lexer.advance();
            return true;
        }

        void _doDash(std::unordered_set<uint8_t>& s)
        {
            uint8_t begin = 0;
            while (!_lexer.matchToken(lex::Token::EOS) && !_lexer.matchToken(lex::Token::CCL_END)) {
                if (!_lexer.matchToken(lex::Token::DASH)) {
                    begin = _lexer.getLexeme();
                    s.insert((uint8_t)begin);
                } else {
                    _lexer.advance();
                    for (; begin <= _lexer.getLexeme() ; ++begin) s.insert((uint8_t)begin);
                }
                _lexer.advance();
            }
        }
        NfaHandler _nfa_handler;
        lex::RegLexer _lexer;
    };
    
} /* namespace nfa */
